//
// Created by liu on 2021/1/29.
//
#include "Det_darknet.h"


namespace alg{

    DarknetDet::DarknetDet(int w, int h){
        confThreshold = 0.5;
        nmsThreshold = 0.4;
        inpWidth = 416;
        inpHeight = 416;
        _image_height = h;
        _image_width = w;
        _net = initnet();
    }
    cv::dnn::Net DarknetDet::initnet(){
        std::string class_names_string = Config::instance().str_conf("coco_names_path");
        std::ifstream class_names_file(class_names_string.c_str());
        if (class_names_file.is_open())
        {
            std::string name = "";
            while (std::getline(class_names_file, name))
            {
                class_names.push_back(name);
            }
        }
        else
        {
            std::cout << "can't open class_names_file!" << endl;
        }

        //给出模型的配置和权重文件
        cv::String modelConfiguration = Config::instance().str_conf("det_cfg_path");
        cv::String modelWeights =  Config::instance().str_conf("det_model_path");

        // 装载网络
        cv::dnn::Net net = cv::dnn::readNetFromDarknet(modelConfiguration, modelWeights);//读取网络模型和参数，初始化网络
        std::cout << "Read Darknet..." << std::endl;
        net.setPreferableBackend(cv::dnn::DNN_BACKEND_OPENCV);
        net.setPreferableTarget(cv::dnn::DNN_TARGET_CPU);  //DNN_TARGET_OPENCL_FP16

        return net;
    }




    int DarknetDet::detect(unsigned char *image, vector<DetObjectStr> &boundRect) {
        cv::Mat frame  = cv::Mat(_image_height,_image_width,CV_8UC3,image,0);
        cv::Mat blob;

        double start = getTickCount();

        // 1/255:将图像像素值缩放到0到1的目标范围
        // Scalar(0, 0, 0):我们不在此处执行任何均值减法，因此将[0,0,0]传递给函数的mean参数
        blob = cv::dnn::blobFromImage(frame, 1 / 255.0, cv::Size (inpWidth, inpHeight), cv::Scalar(0, 0, 0), true, false);

        // 设置网络的输入
        _net.setInput(blob);

        // 运行向前传递以获得输出层的输出
        std::vector<cv::Mat> outs;
        _net.forward(outs, getOutputsNames(_net));//forward需要知道它的结束层

        // 以较低的置信度移除边界框
        postprocess(frame, outs,boundRect );//端到端，输入和输出
    }

    std::vector<cv::String> DarknetDet::getOutputsNames(const cv::dnn::Net& net)
    {
        static vector<String> names;
        if (names.empty())
        {
            //得到输出层的索引号
            std::vector<int> out_layer_indx = net.getUnconnectedOutLayers();

            //得到网络中所有层的名称
            std::vector<String> all_layers_names = net.getLayerNames();

            //在名称中获取输出层的名称
            names.resize(out_layer_indx.size());
            for (int i = 0; i < out_layer_indx.size(); i++)
            {
                names[i] = all_layers_names[out_layer_indx[i] - 1];
            }
        }
        return names;
    }


    void DarknetDet::postprocess(Mat &frame, const vector<Mat> &out, vector<DetObjectStr> &boundRect) {
        std::vector<float> confidences;
        std::vector<Rect> boxes;
        std::vector<int> classIds;

        for (int num = 0; num < out.size(); num++)
        {
            double value;
            Point Position;
            //得到每个输出的数据
            float *data = (float *)out[num].data;

            for (int i = 0; i < out[num].rows; i++, data += out[num].cols)
            {
                //得到每个输出的所有类别的分数
                Mat sorces = out[num].row(i).colRange(5, out[num].cols);

                //获取最大得分的值和位置
                minMaxLoc(sorces, 0, &value, 0, &Position);
                if (value > confThreshold)
                {
                    //data[0],data[1],data[2],data[3]都是相对于原图像的比例
                    int center_x = (int)(data[0] * frame.cols);
                    int center_y = (int)(data[1] * frame.rows);
                    int width = (int)(data[2] * frame.cols);
                    int height = (int)(data[3] * frame.rows);
                    int box_x = center_x - width / 2;
                    int box_y = center_y - height / 2;

                    classIds.push_back(Position.x);
                    confidences.push_back((float)value);
                    boxes.push_back(Rect(box_x, box_y, width, height));
                }
            }
        }

        //执行非极大值抑制，以消除具有较低置信度的冗余重叠框
        std::vector<int> perfect_indx;
        cv::dnn::NMSBoxes(boxes, confidences, confThreshold, nmsThreshold, perfect_indx);

        for (int i = 0; i < perfect_indx.size(); i++)
        {
            DetObjectStr dout;
            int idx = perfect_indx[i];
            float conf = confidences[idx];
            dout.rect= boxes[idx];
            dout.score = confidences[idx];
            dout.label = classIds[idx];
            boundRect.push_back(dout);
        }
    }


}
